﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using RaisingForce.Raise.ObjectModel;
using RaisingForce.Raise.Presentation.Names;
using RaisingForce.Raise.Presentation.Properties;

namespace RaisingForce.Raise.Presentation.ViewModel
{
    /// <summary>
    /// Represents an actionable item displayed by a View (DialogView).
    /// </summary>
    public class ModalDialogViewModel :
        ViewModelBase
    {
        #region Constructors and Initialization Methods

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public ModalDialogViewModel(
            string message,
            DialogMode mode,
            params ICommand[] commands)
        {
            initialize(
                message,
                GetDefaultHeader(),
                translationsCache[mode].ToStrings(),
                commands);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public ModalDialogViewModel(
            string message,
            DialogMode mode,
            params Action[] callbacks)
        {
            initialize(
                message,
                GetDefaultHeader(),
                translationsCache[mode].ToStrings(),
                CreateCommands(callbacks));
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public ModalDialogViewModel(
            string message,
            IDictionary<string, Action> buttons)
        {
            initialize(
                message,
                GetDefaultHeader(),
                buttons.Keys,
                CreateCommands(buttons.Values));
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public ModalDialogViewModel(
            string message,
            string header,
            IDictionary<string, Action> buttons)
        {
            if (buttons == null)
            {
                throw new ArgumentNullException("buttons");
            }
            Contract.EndContractBlock();
            ICommand[] commands = CreateCommands(buttons.Values.ToArray<Action>());
            initialize(message, header, buttons.Keys.ToArray<string>(), commands);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public ModalDialogViewModel(
            string message,
            DialogButtons buttons,
            params ICommand[] commands)
        {
            initialize(
                message,
                GetDefaultHeader(),
                ModalDialogViewModel.GetButtonDefaultStrings(buttons),
                commands);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public ModalDialogViewModel(
            string message,
            string header,
            DialogButtons buttons,
            params ICommand[] commands)
        {
            initialize(
                message,
                header,
                ModalDialogViewModel.GetButtonDefaultStrings(buttons),
                commands);
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public ModalDialogViewModel(
            string message,
            string header,
            ICollection<string> buttons,
            params ICommand[] commands)
        {
            initialize(message, header, buttons, commands);
        }

        private const string dialogDisplayName = "ModalDialog";

        private void initialize(
            string message,
            string header,
            ICollection<string> buttons,
            ICollection<ICommand> commands)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }
            if (buttons == null)
            {
                throw new ArgumentNullException("buttons");
            }
            if (commands == null)
            {
                throw new ArgumentNullException("commands");
            }
            if (buttons.Count != commands.Count)
            {
                throw new ArgumentException(
                    "Same number of buttons and commands expected.");
            }
            Contract.EndContractBlock();
            this.DisplayName = dialogDisplayName;
            this.DialogMessage = message;
            this.DialogHeader = header;
            int index = -1;
            int lastIndex = buttons.Count - 1;
            var buttonCommands = buttons
                .Zip(commands, (b, c) =>
                    {
                        index += 1;
                        return new ButtonViewModel(b, c,
                            isDefault: index == 0,
                            isCancel: index == lastIndex);
                    });
            this.Commands = buttonCommands
                .ToList()
                .AsReadOnly();
        }

        #endregion

        #region Nested types

        /// <summary>
        /// Nested enum symbolizing the types of default buttons used in the dialog -> you can localize those with Localize(DialogMode, string[])
        /// </summary>
        public enum DialogMode
        {
            /// <summary>
            /// Single button in the View (default: OK)
            /// </summary>
            OneButton = 1,
            /// <summary>
            /// Two buttons in the View (default: YesNo)
            /// </summary>
            TwoButton,
            /// <summary>
            /// Three buttons in the View (default: AbortRetryIgnore)
            /// </summary>
            ThreeButton,
            /// <summary>
            /// Four buttons in the View (no default translations, use Translate)
            /// </summary>
            FourButton,
            /// <summary>
            /// Five buttons in the View (no default translations, use Translate)
            /// </summary>
            FiveButton
        }

        /// <summary>
        /// Provides some default button combinations
        /// </summary>
        public enum DialogButtons
        {
            /// <summary>
            /// As System.Window.Forms.MessageBoxButtons Enumeration Ok
            /// </summary>
            Ok,
            /// <summary>
            /// As System.Window.Forms.MessageBoxButtons Enumeration OkCancel
            /// </summary>
            OkCancel,
            /// <summary>
            /// As System.Window.Forms.MessageBoxButtons Enumeration YesNo
            /// </summary>
            YesNo,
            /// <summary>
            /// As System.Window.Forms.MessageBoxButtons Enumeration YesNoCancel
            /// </summary>
            YesNoCancel,
            /// <summary>
            /// As System.Window.Forms.MessageBoxButtons Enumeration AbortRetryIgnore
            /// </summary>
            AbortRetryIgnore,
            /// <summary>
            /// As System.Window.Forms.MessageBoxButtons Enumeration RetryCancel
            /// </summary>
            RetryCancel
        }

        #endregion

        #region Private Declarations

        private static IDictionary<DialogMode, ICollection<StringProxy>> translationsCache; // = null

        #endregion

        #region Static Constructor and Static Methods

        /// <summary>
        /// Static contructor for all DialogViewModels, runs once.
        /// </summary>
        static ModalDialogViewModel()
        {
            initTranslations();
        }

        /// <summary>
        /// Fills the default translations for all modes that we support (use only from static constructor (not thread safe per se)).
        /// </summary>
        private static void initTranslations()
        {
            translationsCache = Enum.GetValues(typeof(DialogMode))
                .Cast<DialogMode>()
                .ToDictionary(
                    mode => mode,
                    mode => (ICollection<StringProxy>)getDefaultTranslations(mode));
        }

        public static string GetDefaultHeader()
        {
            return Application.Current.MainWindow
                .GetType().Assembly
                .GetName().Name;
        }

        /// <summary>
        /// Creates a dictionary symbolizing buttons for given dialog mode and buttons names with actions to berform on each.
        /// </summary>
        /// <param name="mode">Mode that tells how many buttons are in the dialog.</param>
        /// <param name="names">Names of buttons in sequential order.</param>
        /// <param name="callbacks">Callbacks for given buttons.</param>
        /// <returns></returns>
        public static IDictionary<string, Action> CreateButtons(
            DialogMode mode,
            ICollection<string> names,
            params Action[] callbacks)
        {
            int modeNumButtons = (int)mode;
            if (names.Count != modeNumButtons)
            {
                throw new ArgumentException(
                    Resources.Exception_ModalDialog_InvalidNames,
                    "names");
            }
            if (callbacks.Length != modeNumButtons)
            {
                throw new ArgumentException(
                    Resources.Exception_ModalDialog_InvalidCallbacks,
                    "callbacks");
            }
            return names
                .Zip(callbacks, (n, c) => new { Key = n, Value = c })
                .ToDictionary(x => x.Key, x => x.Value);
        }

        /// <summary>
        /// Creates Commands for given enumeration of Actions.
        /// </summary>
        /// <param name="actions">Actions to create commands from.</param>
        /// <returns>Array of commands for given actions.</returns>
        public static ICommand[] CreateCommands(IEnumerable<Action> actions)
        {
            return actions
                .Select(action => new DelegateCommand(p => action()))
                .ToArray();
        }

        /// <summary>
        /// Creates string for some predefined buttons (English).
        /// </summary>
        /// <param name="buttons">DialogButtons enumeration value.</param>
        /// <returns>String array for desired buttons.</returns>
        public static string[] GetButtonDefaultStrings(DialogButtons buttons)
        {
            return getButtonDefaultProxies(buttons).ToStrings();
        }

        private static StringProxy[] getButtonDefaultProxies(DialogButtons buttons)
        {
            switch (buttons)
            {
                case DialogButtons.Ok:
                    return new StringProxy[] { ButtonNames.OK };
                case DialogButtons.OkCancel:
                    return new StringProxy[] { ButtonNames.OK, ButtonNames.Cancel };
                case DialogButtons.YesNo:
                    return new StringProxy[] { ButtonNames.Yes, ButtonNames.No };
                case DialogButtons.YesNoCancel:
                    return new StringProxy[] { ButtonNames.Yes, ButtonNames.No, ButtonNames.Cancel };
                case DialogButtons.RetryCancel:
                    return new StringProxy[] { ButtonNames.Retry, ButtonNames.Cancel };
                case DialogButtons.AbortRetryIgnore:
                    return new StringProxy[] { ButtonNames.Abort, ButtonNames.Retry, ButtonNames.Ignore };
                default:
                    throw new InvalidOperationException(
                        "There are no default string translations for this button configuration.");
            }
        }

        private static StringProxy[] getDefaultTranslations(DialogMode mode)
        {
            StringProxy[] translated;
            switch (mode)
            {
                case DialogMode.OneButton:
                    translated = getButtonDefaultProxies(DialogButtons.Ok);
                    break;
                case DialogMode.TwoButton:
                    translated = getButtonDefaultProxies(DialogButtons.YesNo);
                    break;
                case DialogMode.ThreeButton:
                    translated = getButtonDefaultProxies(DialogButtons.YesNoCancel);
                    break;
                default:
                    translated = null; // you should use Translate() for this combination (ie. there is no default for four or more buttons)
                    break;
            }
            return translated;
        }

        /// <summary>
        /// Translates all the Dialogs with specified mode.
        /// </summary>
        /// <param name="mode">Dialog mode / type.</param>
        /// <param name="translations">Array of translations matching the buttons in the mode.</param>
        public static void Translate(DialogMode mode, ICollection<StringProxy> translations)
        {
            lock (translationsCache)
            {
                if (translations.Count != (int)mode)
                {
                    throw new ArgumentException(
                        "Wrong number of translations for selected mode");
                }
                if (translationsCache.ContainsKey(mode))
                {
                    translationsCache.Remove(mode);
                }
                translationsCache.Add(mode, translations);
            }
        }

        #endregion

        #region ViewModel Properties

        private bool isErrorDialog;   // = false

        public bool IsErrorDialog
        {
            get { return this.isErrorDialog; }
            set
            {
                this.SetProperty(
                    ref this.isErrorDialog,
                    value,
                    "IsErrorDialog");
            }
        }

        private bool dialogShown;

        /// <summary>
        /// Checks if the dialog is visible, use Show() Hide() methods to set this
        /// </summary>
        public bool DialogShown
        {
            get { return this.dialogShown; }
            private set
            {
                this.SetProperty(
                    ref this.dialogShown,
                    value,
                    "DialogShown");
            }
        }

        private string dialogMessage;

        /// <summary>
        /// The message shown in the dialog
        /// </summary>
        public string DialogMessage
        {
            get { return this.dialogMessage; }
            private set
            {
                this.SetProperty(
                    ref this.dialogMessage,
                    value,
                    "DialogMessage");
            }
        }

        private ViewModelBase dialogContent;

        public ViewModelBase DialogContent
        {
            get { return this.dialogContent; }
            set
            {
                this.SetProperty(
                    ref this.dialogContent,
                    value,
                    "DialogContent");
            }
        }

        private int dialogContentMinWidth;  // = 0

        public int DialogContentMinWidth
        {
            get { return this.dialogContentMinWidth; }
            set
            {
                this.SetProperty(
                    ref this.dialogContentMinWidth,
                    value,
                    "DialogContentMinWidth");
            }
        }

        private string dialogHeader;

        /// <summary>
        /// The header (title) of the dialog
        /// </summary>
        public string DialogHeader
        {
            get { return this.dialogHeader; }
            private set
            {
                this.SetProperty(
                    ref this.dialogHeader,
                    value,
                    "DialogHeader");
            }
        }

        private ReadOnlyCollection<ButtonViewModel> commands;

        /// <summary>
        /// Commands this dialog calls (the models that it binds to)
        /// </summary>
        public ReadOnlyCollection<ButtonViewModel> Commands
        {
            get { return this.commands; }
            private set
            {
                this.SetProperty(
                    ref this.commands,
                    value,
                    "Commands");
            }
        }

        #endregion

        #region Public Methods

        public void Show()
        {
            this.DialogShown = true;
        }

        public void Hide()
        {
            this.DialogShown = false;
            this.dialogMessage = string.Empty;
        }

        #endregion
    }
}
